
//Copyright 1997-2009 Syrinx Development, Inc.
//This file is part of the Syrinx Web Application Framework (SWAF).
// == BEGIN LICENSE ==
//
// Licensed under the terms of any of the following licenses at your
// choice:
//
//  - GNU General Public License Version 3 or later (the "GPL")
//    http://www.gnu.org/licenses/gpl.html
//
//  - GNU Lesser General Public License Version 3 or later (the "LGPL")
//    http://www.gnu.org/licenses/lgpl.html
//
//  - Mozilla Public License Version 1.1 or later (the "MPL")
//    http://www.mozilla.org/MPL/MPL-1.1.html
//
// == END LICENSE ==
using System;
using System.Diagnostics;

using Swaf;
using Swaf.BizObj;

namespace Swaf.Crypto
{
	/// <summary>
	/// Provides the ability to encrypt a bizobj field value in memory and to control
	/// how get/put expect their value to be encrypted or not.</summary>
	/// <remarks>
	/// Supports two values when applied as a decorator: 
	/// Full - Indicates that the value is encrypted in memory and that calls to 
	/// get/put with extraInfo == ExternalUntrusted and ExternalTrusted will 
	/// use encrypted values.
	/// 
	/// True - Indicates that the value is encrypted in memory and that calls to
	/// get/put with extraInfo == ExternalUntrusted will use encrypted values
	/// while extraInfo == ExternalTrusted will use unencrypted values.</remarks>
	[Serializable]
	public class CryptoBizObjDecorator : GenericDecorator, IBizObjDecorator
	{
		protected enum CryptoBehavior { DoNothing, EncryptValue, DecryptValue };
		protected enum CryptoValType { Full, True, None };

		protected static ICryptoManager s_cryptoMgr = null;
		/// <summary>
		/// This variable is used to indicate if the value stored in the bizobj
		/// is stored encrypted or not.  If m_encryptBase is true, the value
		/// stored in the bizobj is encrypted, else the value is stored unencrypted.
		/// </summary>
		protected CryptoValType m_cryptoVal = CryptoValType.None;

		/// <summary>
		/// Standard constructor for bizobj field decorator types.  Expects val
		/// to be either full or true, otherwise will throw a new CryptoException.</summary>
		/// <param name="name">Typically encrypt, but this class doesn't care what the
		/// name of the decorator was that triggered the creation of an instance.</param>
		/// <param name="val">Must be full or true otherwise a CryptoException
		/// is thrown.</param>
		/// <param name="otherInfo">Not used in this implementation but needed
		/// for proper class factory creation.</param>
		public CryptoBizObjDecorator(string name, string val, object otherInfo)
			:base(name, val, otherInfo)
		{
			IBizObjField field = otherInfo as IBizObjField;
			Debug.Assert(field != null);

			try
			{
				m_cryptoVal = (CryptoValType) Enum.Parse(typeof(CryptoValType), m_val, true);
			}
			catch (Exception e)
			{
				throw new CryptoException(
					String.Format("Invalid value '{0}' specified for encrypt bizobj field decorator.  Valid values include 'full' and 'true'",
					val), e);
			}

			// Regardless of what the encrypt decorator specifies, if the decorator
			// is placed on a Method rather than a Field, we are going to ignore it.
			if (field.BizType == BizFieldType.Method)
			{
				m_cryptoVal = CryptoValType.None;
			}
		}

		public static ICryptoManager CryptoManager
		{
			get
			{
				if (s_cryptoMgr == null)
				{
					s_cryptoMgr = Application.currentApp.appData["sysObjs-Crypto"] as ICryptoManager;
				}

				return s_cryptoMgr;
			}
		}

		#region IBizObjDecorator Members

		/// <summary>
		/// Will encrypt or decrypt the provided value depending on the value of extraInfo
		/// and the value used during decoration.
		/// 
		/// The guidelines for putting the val are as follows:
		/// 
		/// A. If extraInfo is ExternalUntrusted
		///		Field with encrypt='true' should already be encrypted, if not encrypted throw an error
		///		Field with encrypt='full' should already be encrypted, if not encrypted throw an error
		///		
		///	B. If extraInfo is ExternaTrusted
		///		Field with encrypt='true' should be clear text, so encrypt
		///		Field with encrypt='full' should already be encrypted, if not encrypted try and encrypt it
		///		
		///	C. If extraInfo is "encrypt"
		///		Field with encrypt='true' if val isn't encrypted, encrypt it
		///		Field with encrypt='full' if val isn't encrypted, encrypt it
		///
		///	D. If extraInfo is not supplied
		///		Field with encrypt='true' should be clear text, so encrypt
		///		Field with encrypt='full' should be clear text, so encrypt
		/// </summary>
		/// <seealso cref="CryptoBizObjDecorator"/>
		public void put(IBizObj parent, IBizObjField field, ref object val, object extraInfo, ref bool shouldBeFieldType)
		{
			if (val != null)
			{
				// Assume value with be a field type
				shouldBeFieldType = true;
				
				// Get the crypto directions from the extra info
				string cryptoDirections = (extraInfo == null) ? string.Empty : extraInfo.ToString().ToLower();

				switch (cryptoDirections)
				{
					case "externaluntrusted":
						if (m_cryptoVal == CryptoValType.True || m_cryptoVal == CryptoValType.Full)
						{
							// Ensure val is encrypted
							if (! CryptoManager.isValueEncrypted(val.ToString()))
							{
								throw new CryptoException(string.Format("ExternalUntrusted specified and BizObj Field {0}.{1} is not encrypted", parent.type.Name, field.Name));
							}

							// Full and True fields from untrusted sources are assumed to already be encrypted
							shouldBeFieldType = false;
						}
						break;
					case "externaltrusted":
						if (m_cryptoVal == CryptoValType.True)
						{
							// True fields from trusted sources are assumed to be clear text
							val = CryptoManager.encrypt(BizObjManager.BizObjCryptoKeyName, val.ToString());

							// Just encrypted the field so it won't be the FielType
							shouldBeFieldType = false;
						}
						else if (m_cryptoVal == CryptoValType.Full)
						{
							// Encrypt if the val isn't already encrypted
							if (! CryptoManager.isValueEncrypted(val.ToString()))
							{
								// If not encrypted tryp to encrypt it
								val = CryptoManager.encrypt(BizObjManager.BizObjCryptoKeyName, val.ToString());
							}	

							// Full fields from trusted sources are assumed to already be encrypted
							shouldBeFieldType = false;
						}
						break;
					case "encrypt":
						if (m_cryptoVal == CryptoValType.True || m_cryptoVal == CryptoValType.Full)
						{
							// Encrypt if the val isn't already encrypted
							if (! CryptoManager.isValueEncrypted(val.ToString()))
							{
								// Field was requested to be encrypted on the put
								val = CryptoManager.encrypt(Swaf.BizObj.BizObjManager.BizObjCryptoKeyName, val.ToString());
							}

							// Just encrypted the field so it won't be the FielType
							shouldBeFieldType = false;
						}					
						break;
					default:
						if (m_cryptoVal == CryptoValType.True || m_cryptoVal == CryptoValType.Full)
						{
							// No encrypt directions provided, so assume the value needs to be encrypted
							val = CryptoManager.encrypt(Swaf.BizObj.BizObjManager.BizObjCryptoKeyName, val.ToString());

							shouldBeFieldType = false;
						}	
						
						break;
				}
			}
		}


		/// <summary>
		/// Will encrypt or decrypt the provided value depending on the value of extraInfo
		/// and the value used during decoration.</summary>
		/// 
		/// The guidelines for getting the val are as follows

		/// A. If extraInfo is ExternalUntrusted
		///		Field with encrypt='true' should be encrypted in memory, return as is
		///		Field with encrypt='full' should be encrypted in memory, return as is
		///		
		///	B. If extraInfo is ExternaTrusted
		///		Field with encrypt='true' should be encrypted in memory, return decrypted
		///		Field with encrypt='full' should be encrypted in memory, returne as is
		///	
		///	C. If extraInfo is "encrypt"
		///		Field with encrypt='true' should be encrypted in memory, return as is
		///		Field with encrypt='full' should be encrypted in memory, return as is
		///		
		///	D. If extraInfo is not supplied
		///		Field with encrypt='true' should be encrypted in memory, return decrypted
		///		Field with encrypt='full' should be encrypted in memory, return decrypted
		/// <seealso cref="CryptoBizObjDecorator"/>
		public void get(IBizObj parent, IBizObjField field, ref object val, object[] extraInfo, ref bool shouldBeFieldType)
		{
			if (val != null)
			{
				// Assume value with be a field type
				shouldBeFieldType = true;
				CryptoBehavior behavior = CryptoBehavior.DoNothing;

				// Get the crypto directions from the extra info
				string cryptoDirections = (extraInfo != null && extraInfo.Length != 0) ? extraInfo[0].ToString().ToLower() : string.Empty;

				if (cryptoDirections == "externaluntrusted" || cryptoDirections == "encrypt")
				{
					// Full and True fields are encrypted in memory (unless on a method)so the
					// current value is what should be returned and they will be encrypted strings,
					// therefore not field types.
					if (m_cryptoVal == CryptoValType.True || m_cryptoVal == CryptoValType.Full)
					{
						shouldBeFieldType = false;
					}
				}
				else if (cryptoDirections == "externaltrusted")
				{
					if (m_cryptoVal == CryptoValType.True)
					{
						// Decrypt the True field to its Field type an return
						behavior = CryptoBehavior.DecryptValue;
					}
					else if (m_cryptoVal == CryptoValType.Full)
					{
						// Full will be encrypted so it won't be a field type
						shouldBeFieldType = false;
					}
				}
				else if (m_cryptoVal == CryptoValType.True || m_cryptoVal == CryptoValType.Full)
				{
					// The default with no encrypt directions is to decrypt the value
					// Decrypt the field to its Field type an return
					behavior = CryptoBehavior.DecryptValue;
				}

				// Perform cryptography operation if necessary
				if (behavior == CryptoBehavior.EncryptValue)
				{
					val = CryptoManager.encrypt(BizObjManager.BizObjCryptoKeyName, val.ToString());
				}
				else if (behavior == CryptoBehavior.DecryptValue)
				{
					val = CryptoManager.decrypt(BizObjManager.BizObjCryptoKeyName, val.ToString());
				}
			}
		}

		/// <summary>
		/// This is used by XML serialization of bizobjs to control whether the decorator consumes the
		/// entire XML element for the field (OuterXML) or just the inner xml element content
		/// (InnerXML).  When encryption is applied to a field it is always using the inner xml content
		/// </summary>
		public bool doesUseFullXmlElement()
		{
			return ! (m_cryptoVal == CryptoValType.True || m_cryptoVal == CryptoValType.Full);
		}
		#endregion

		/// <summary>
		/// Indicates that this decorator manipulates the field value for the bizobj.  This
		/// decorator is always a stand-in.</summary>
		public override bool IsStandin { get { return true; } }
	}
}

